#include <string.h>

#include "lib.h"

#include "rlconn.h"
#include "rlround.h"
#include "rlbet.h"
#include "rlguts.h"
#include "rlsql.h"
#include "log.h"

#include "stdncerr.h"
#include "badpacketmsgs.h"
#include "RLXMLTranslator.h"

#ifdef FORCED_ROULETTE
#include "opts.h"
#endif

RouletteConnection::RouletteConnection(Server* server, SOCKET client)
	: Connection(server, client)
{
	guts = (RouletteGuts*)0;

	// purposefully set maxBet < minBet to disallow playing
	// games where these values don't come from database
	//
	minInsideBet = 500;
	maxInsideBet = 1;
	minOutsideBet = 100;
	maxOutsideBet = 1;
	compFactor = 0;

	loggedOn = false;

	casinoSpecific = (void*)0;

    // kharmon.  11-15-06.  #16921.  Zero out buffers before using them.
    msglen = 0;
    memset(msgbuf, 0, sizeof(msgbuf));

#ifdef FORCED_ROULETTE
	forcedGuts = (RouletteForcedGuts*)0;
	forcedSpinsLoaded = 0;
#endif
}

RouletteConnection::~RouletteConnection()
{
	// CSN-7075: New Authentication
	trackLogoutSession();
	
	removeCasinoSpecific();

#ifdef FORCED_ROULETTE
		delete forcedGuts;
		forcedGuts = (RouletteForcedGuts*)0;
#endif

	loggedOn = false;
}

void
RouletteConnection::msgStart()
{
	msglen = 0;
}

void
RouletteConnection::msgAdd(uint8 Byte)
{
	msgbuf[msglen++] = Byte;
}

void
RouletteConnection::msgAdd(int32 SignedLong)
{
	msgbuf[msglen++] = (uint8)((SignedLong) & 0xff);
	msgbuf[msglen++] = (uint8)(((SignedLong) >> 8) & 0xff);
	msgbuf[msglen++] = (uint8)(((SignedLong) >> 16) & 0xff);
	msgbuf[msglen++] = (uint8)(((SignedLong) >> 24) & 0xff);
}

uint8*
RouletteConnection::msgGetBuf()
{
	return msgbuf + msglen;
}

void
RouletteConnection::msgReleaseBuf(int32 Amt)
{
	msglen += Amt;
}

void
RouletteConnection::msgEnd()
{
	msglen = encode(msgbuf, obuf + oused + 1, msglen);
	msglen++;	// include msg len byte
	obuf[oused] = (uint8)(msglen & 0xff);
	oused += msglen;
}

void
RouletteConnection::sendStopMsg()
{
	msgStart();
	msgAdd((uint8)'!');
	strcpy((char*)msgGetBuf(), (char*)getServerStopMsg());
	msgReleaseBuf(strlen(getServerStopMsg()));
	msgEnd();
}

int32
RouletteConnection::msgRemove(uint8* Src, int32* pResult)
{
	*pResult = (int32)(
		(Src[0] & 0xFF) |
		((Src[1] << 8) & 0xFF00) |
		((Src[2] << 16) & 0xFF0000) |
		((Src[3] << 24) & 0xFF000000));

	return 4;
}

int32
RouletteConnection::msgRemove(uint8* Src, uint8* pResult)
{
	*pResult = *Src;

	return 1;
}

bool8
RouletteConnection::getResponse()
{
	int32 ieat = 0, remain = iused, len, srclen;
	uint8* src = ibuf;
	int32 balance;

	// [AO 2007-10-01] We can send just one reply for certain repeating messages.
	// We're not handling partial buffers and incomplete messages but that's OK.
	char last_message = 0;

	while (remain >= 2)
	{
		srclen = src[0] + (src[1] << 8);

		if (remain < srclen)
			break;

		// Handle an invalidly formatted message of length zero.
		if (srclen <= 2)    // kharmon.  11-16-06.  #16921.
		{
			ieat += 2;
			remain -= 2;
			src += 2;
			continue;
		}

        if(srclen>sizeof(msgbuf))
        {
            // if the message is too big to fit into our
            // buffer...that can't be right.  So just nuke everything 
            // and quit.
			ieat = iused;
			break;
        }

        // kharmon.  11-15-06.  #16921.  Zero out buffers before using them.
	    msglen = 0;
	    memset(msgbuf, 0, sizeof(msgbuf));

		len = decode(src + 2, msgbuf, srclen - 1);
		ieat += srclen;
		remain -= srclen;
		src += srclen;

		switch (msgbuf[0])
		{
			case 'L':
			{
				int32 err = 0;

				if (!parseCasinoLogin(6, &err))
				{
					msgStart();
					msgAdd((uint8)'E');
					msgAdd((uint8)(err & 0xff));
					msgEnd();
					break;
				}

				handleLogin(len);
				break;
			}

			case 'S':
			{
				int32 i;
				uint8 numBets;
				uint8 wagerID;
				int32 used;
				int32 bet=0;
				bool8 abort;
				RouletteRound *round;
				RouletteBet *rlbet;

				if (!loggedOn)
				{
					Log::msgWarning("Roulette Spin: not logged on..."
						" invalid bet  ");

					trackaddBadPacket(TRY_SPIN_NOT_LOGGED_ON, msgbuf[0],
						(char*)msgbuf, len);

					msgStart();
					msgAdd((uint8)'B');
					msgEnd();

					break;
				}

				if (isServerStopping())
				{
					sendStopMsg();
					break;
				}

				// [AO 2008-01-16] HA: verify the user is still logged on
				if (!ValidateSession())
					break;

				round = new RouletteRound();
				abort = (bool8)(round == (RouletteRound*)0);

				used = 1;
				used += msgRemove(msgbuf + used, &numBets);

				for (i = 0; !abort && i < numBets; i++)
				{
					used += msgRemove(msgbuf + used, &bet);
					used += msgRemove(msgbuf + used, &wagerID);

					if (guts->validateBet(wagerID))
					{
						rlbet = guts->newBet(wagerID, bet);
						if (rlbet)
							round->addBet(rlbet);
						else
						{
							abort = true;
							Log::msgError("Roulette Spin: failed to create"
								" wager ID: %d, amount: %ld", wagerID, bet);
						}
					}
					else
					{
						abort = true;
						Log::msgError("Roulette Spin: invalid wager ID: %d,"
							" amount: %ld", wagerID, bet);
					}
				}

				if (!abort)
				{
					trackGetBalance(&balance);

					if (!round->validate(minInsideBet, maxInsideBet,
										 minOutsideBet, maxOutsideBet, maxSingleInsideBet))
					{
						Log::msgWarning("Roulette Spin: invalid bet: %ld", bet);

						msgStart();
						msgAdd((uint8)'B');
						msgEnd();
					}
					else if (balance < round->totalBet())	// not enough money
					{
						Log::msgWarning("Roulette Spin: out of money");

						msgStart();
						msgAdd((uint8)'$');
						msgEnd();
					}
					else
					{
						uint8 spinresult;
						int32 payout, winBetsTotal;

// CSN-12125 - Forced spin
#ifndef FORCED_ROULETTE
						spinresult = guts->spin();
#else
						if (ServerOpts::bAllowTesting && forcedSpinsLoaded)
						{
							spinresult = forcedGuts->spin();							
						}
						else
						{
							spinresult = guts->spin();
						}
#endif
						payout = round->calcPayout(spinresult);
						winBetsTotal = round->calcTotalWinningBets(spinresult);

						if (trackSpin(spinresult, payout, round, &balance))
						{
							msgStart();
							msgAdd((uint8)'S');
							msgAdd(balance);
							msgAdd(winBetsTotal);
							msgAdd(payout);
							msgAdd(spinresult);
							msgEnd();
						}
						else
						{
							Log::msgError("Roulette Spin: sql problem..."
								"game aborted");

							msgStart();
							msgAdd((uint8)'N');
							msgEnd();
						}
					}
				}
				else
				{
					if (!round)
						Log::msgError("Roulette Spin: failed to create Round");

					msgStart();
					msgAdd((uint8)'N');
					msgEnd();
				}

				delete round;
				break;
			}

			case 'M':	//Unicode
			{
				switch (msgbuf[1])
				{
					case 'L':
					{
						int32 err = 0;

						if (!parseUnicodeCasinoLogin(7, &err))
						{
							msgStart();
							msgAdd((uint8)'E');
							msgAdd((uint8)(err & 0xff));
							msgEnd();
							break;
						}

						handleLogin(len);

						break;
					}
				}
				break;
			}

            case 'm':  // Balance Request
            {
	            if (isServerStopping())
	            {
		            sendStopMsg();
		            break;
	            }

				// [AO 2007-10-01] Ignore repeating messages
				// [AO 2008-01-16] HA: verify the user is still logged on
				if (msgbuf[0] == last_message || !ValidateSession())
					break;

	            //rule1: gotta be logged on
                if( loggedOn && trackGetBalance(&balance) )
                {
                    msgStart();
                    msgAdd((uint8)'m');
                    msgAdd((int32)balance);
                    msgEnd();
                }

	            break;
            }

			default:
				trackaddBadPacket(GETREPONSE_DEFAULT_CASE, msgbuf[0],
					(char*)msgbuf, len);

				Log::msgWarning("Roulette::getResponse:  Unknown input"
					" (0x%02x)  ", msgbuf[0]);
				msgStart();
				msgAdd((uint8)'N');
				msgEnd();
				break;
		}

		last_message = msgbuf[0]; // [AO 2007-10-01] Handle repeating messages
	}

	if (iused <= ieat) // [AO 2007-10-01] Avoid buffer overflow
		iused = 0;
	else
	{
		memmove(ibuf, ibuf + ieat, iused - ieat);
		iused -= ieat;
	}

	return (bool8)(oused != 0);
}

void
RouletteConnection::handleLogin(int32 len)
{
	int32 balance;

	if (isServerStopping())
	{
		sendStopMsg();
		return;
	}

	if (!loggedOn)
	{
		guts = RouletteGuts::getGuts(getTableID());

#ifdef FORCED_ROULETTE
		// CSN-12125 - Load forced spins only if Testing is enabled
		if (ServerOpts::bAllowTesting)
		{
			forcedGuts        = new RouletteForcedGuts(getTableID());
			forcedSpinsLoaded = forcedGuts->load(getTableID(), getPid());
		}
#endif

		if (guts != (RouletteGuts*)0 &&
			trackGetPlayerLimits(&minInsideBet, &maxInsideBet,
				&minOutsideBet, &maxOutsideBet, &maxSingleInsideBet, &compFactor) &&
			trackGetBalance(&balance) 	)
		{
			loggedOn = true;

			msgStart();
			msgAdd((uint8)'L');
			msgAdd(balance);
			msgAdd(minInsideBet);
			msgAdd(maxInsideBet);
			msgAdd(minOutsideBet);
			msgAdd(maxOutsideBet);
			msgAdd(maxSingleInsideBet);
			msgEnd();
		}
		else
		{
			if (!guts)
				Log::msgError("Roulette Login: bad guts: %c  ",
					msgbuf[5]);
			else
				Log::msgError("Roulette Login: failed to "
					"get min/max limits ");

			msgStart();
			msgAdd((uint8)'N');
			msgEnd();

			guts = (RouletteGuts*)0;
		}
	}
	else
	{
		Log::msgWarning("Roulette Login: attempt to re-log on  ");

		trackaddBadPacket(TRY_LOGON_WHEN_LOGGED_ON, msgbuf[0],
			(char*)msgbuf, len);

		msgStart();
		msgAdd((uint8)'N');
		msgEnd();
	}
}

void
RouletteConnection::allocateXMLTranslator( XMLTranslator** target )
{
	*target = new RLXMLTranslator( getServer(), this );
}

// [AO 2008-01-16] Refactoring for HA
bool RouletteConnection::ValidateSession()
{
	int32 session_check = trackValidateSession();
	if (!session_check) // session is valid
		return true;

	// [AO 2008-01-22] Additional case from RTG 6.6.0 code drop
	if (session_check == RouletteSQL::NormalLogout)
		session_check = LOGINERR_NOTLOGGEDIN;

	msgStart();
	msgAdd(static_cast<uint8>('N'));
	msgAdd(static_cast<int32>(session_check));
	msgEnd();
	return false;
}
